﻿/* HEADER
 * ------
 * © 2009 by Salomon Zwecker 
 * modified by:
 * - 
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace Shapes.Geometry
{
    /// <summary>
    /// this enum describes how a shape in a ShapeComposition is combined with the shapes below
    /// </summary>
    public enum ShapeComposeFunction
    {
        /// <summary>
        /// Adds a shape to the shapes below
        /// </summary>
        Add = 0,
        /// <summary>
        /// Subtracts (cut out) the shape from the shapes below
        /// </summary>
        Subtract = 1,
        /// <summary>
        /// Inverts everything below (where a shape was, there is nothing, where nothing was is a shape)
        /// </summary>
        Invert = 2,
        /// <summary>
        /// Masks the shapes below (everything outside this shape will not beeing processed)
        /// </summary>
        Mask = 3
    }
    /// <summary>
    /// a shape defined by a collection of other shapes
    /// </summary>
    public sealed class ShapeComposition : Shape
    {
        internal class ShapeObject
        {
            internal Shape _Shape;
            internal ShapeComposeFunction _Function;
        }
        internal List<ShapeObject> _Objects = new List<ShapeObject>();

        /// <summary>
        /// Creates a new ShapeComposition
        /// </summary>
        /// <param name="basicShape">the first Shape of the Composition</param>
        public ShapeComposition(Shape basicShape)
            : base()
        {

            Add(basicShape, ShapeComposeFunction.Add);

            //_Transform = new Transformation2D(new Vector2(basicShape._Transform.Left, basicShape._Transform.Up),
            //    basicShape._Transform.Right - basicShape._Transform.Left,
            //    basicShape._Transform.Down - basicShape._Transform.Up);

            //basicShape.Position -= _Transform.Position;

        }

        /// <summary>
        /// Adds a new shape to the composition.
        /// </summary>
        /// <param name="shape">the Shape to add</param>
        /// <param name="function">how the shape is composed. The function has an influence to all sapes added before.</param>
        public void Add(Shape shape, ShapeComposeFunction function)
        {
            Add(shape, function, true);
        }
        internal void Add(Shape shape, ShapeComposeFunction function, bool repositioning)
        {
           // if(repositioning)
                shape.Position = _Transform.TransformGlobalToLocal(shape.Position);
            _Objects.Insert(0, new ShapeObject() { _Shape = shape, _Function = function });
            shape.OnChangeGeometry += new Action<Drawing>(shape_OnChangeGeometry);
            shape._Transform.OnChange += new Action<Transformation2D>(Bounding_OnChange);

           
            ResizeBounding(_Objects[0]);
            CallOnChange();
        }

        void Bounding_OnChange(Transformation2D obj)
        {
            foreach (ShapeObject o in _Objects)
            {
                if (o._Shape._Transform == obj)
                {
                    ResizeBounding(o);
                    break;
                }
            }
        }

        void shape_OnChangeGeometry(Drawing obj)
        {
            //Shape shape = obj as Shape;
            //foreach (ShapeObject o in _Objects)
            //{
            //    if (o._Shape == shape)
            //    {
            //        ResizeBounding(o);
            //        break;
            //    }
            //}
            Bounding_OnChange(obj._Transform);
            CallOnChange();
        }

        private void ResizeBounding(ShapeObject changedShape)
        {
            
            Vector2 pos = Vector2.Zero;
            Vector2 dim = new Vector2(_Transform.Width, _Transform.Height);

            // calculate position and dimension offset
            foreach (ShapeObject o in _Objects)
            {
                if (o._Function == ShapeComposeFunction.Mask)
                {
                    pos = new Vector2(
                          o._Shape._Transform.Left,//_Bounding._Position.X,// - 
                          o._Shape._Transform.Up);//_Bounding._Position.Y);// - 

                    _Transform._Position.X += o._Shape._Transform.Left;
                    _Transform._Position.Y += o._Shape._Transform.Up;

                    _Transform._Width = o._Shape._Transform.Right - o._Shape._Transform.Left;
                    _Transform._Height = o._Shape._Transform.Down - o._Shape._Transform.Up;

                    goto ShapeRepositioning;
                }

                if(o == changedShape)// && o._Function != ShapeComposeFunction.Subtract)
                {
                    if (o._Function == ShapeComposeFunction.Subtract)
                        return;

                    if (changedShape._Shape._Transform.Left < 0)
                        pos.X = changedShape._Shape._Transform.Left;
                    if (changedShape._Shape._Transform.Up < 0)
                        pos.Y = changedShape._Shape._Transform.Up;

                    if (changedShape._Shape._Transform.Right > dim.X)
                        dim.X = changedShape._Shape._Transform.Right;
                    if (changedShape._Shape._Transform.Down > dim.Y)
                        dim.Y = changedShape._Shape._Transform.Down;
                 
                    break;
                }
            }

            // Fit Bounding
            if (pos != Vector2.Zero || dim.X != _Transform.Width || dim.Y != _Transform.Height)
            {
                if(pos != -_Transform._Position)
                    _Transform._Position += pos;
                _Transform._Width = dim.X - pos.X;
                _Transform._Height = dim.Y - pos.Y;

                _Transform.CallOnChange();
            }

            // shift all shapes if required
            pos.X = (pos.X < 0) ? pos.X : 0;
            pos.Y = (pos.Y < 0) ? pos.Y : 0;

        ShapeRepositioning:
            if (pos != Vector2.Zero)
            {
                foreach (ShapeObject o in _Objects)
                {
                    o._Shape._Transform._Position -= pos - o._Shape._Transform._Origin;
                }
            }
        }

        internal override bool IsPointInside(ref Vector2 point)
        {
            bool isInverted = false;

            for (int i = 0; i < _Objects.Count; i++)
            {
                Shape shape = _Objects[i]._Shape;

                switch (_Objects[i]._Function)
                {
                    case ShapeComposeFunction.Add:
                        if (shape.IsPointInside(point))
                            return !isInverted;
                        break;
                    case ShapeComposeFunction.Subtract:
                        if (shape.IsPointInside(point))
                            return isInverted;
                        break;
                    case ShapeComposeFunction.Invert:
                        if (shape.IsPointInside(point))
                            isInverted = !isInverted;
                        break;
                    case ShapeComposeFunction.Mask:
                        if (!shape.IsPointInside(point))
                            return isInverted;
                        break;
                }
            }
            return isInverted;
        }

        #region nearest point / distance edge
        internal override float GetDistanceToEdge(ref Vector2 point)
        {
            ShapeObject o;
            float dist;
            Vector2 nearPoint;

            _Transform.TransformGlobalToLocal(ref point);

            GetNearestBorderedShape(ref point, out o, out dist, out nearPoint);

            return dist;
        }
        internal override Vector2 GetNearestPointOnEdge(ref Vector2 point)
        {
            ShapeObject o;
            float dist;
            Vector2 nearPoint;

            _Transform.TransformGlobalToLocal(ref point);

            GetNearestBorderedShape(ref point, out o, out dist, out nearPoint);

            return nearPoint;
        }

        void GetNearestBorderedShape(ref Vector2 point, out ShapeObject nearestObject, out float distance, out Vector2 nearestPoint)
        {
            bool[] outs = new bool[_Objects.Count];

            while (outs.Contains(false))
            {
                // get nearest shape
                GetNearestShape(ref point, ref outs, out nearestObject, out distance, out nearestPoint);

                // find own index
                int idx;
                for(idx = 0; _Objects[idx] != nearestObject; idx++) { }

                // check below
                bool isStuffBehind = false;
                for (int i = _Objects.Count - 1; i > idx; i--)
                //for(int i = idx + 1; i < _Objects.Count; i++)
                {
                    if (_Objects[i]._Shape.IsPointInside( point))
                    {
                        switch (_Objects[i]._Function)
                        {
                            case ShapeComposeFunction.Add:
                                isStuffBehind = true;
                                break;
                            case ShapeComposeFunction.Subtract:
                                isStuffBehind = false;
                                break;
                            case ShapeComposeFunction.Invert:
                                isStuffBehind = !isStuffBehind;
                                break;
                            default:
                                break;
                        }
                    }
                    else if(_Objects[i]._Function == ShapeComposeFunction.Mask)
                    {
                        isStuffBehind = false;
                    }
                }

                // check self
                switch (nearestObject._Function)
                {
                    case ShapeComposeFunction.Add:
                        if (isStuffBehind)
                        {
                            goto ContinueMainLoop;
                        }
                        break;
                    case ShapeComposeFunction.Subtract:
                    case ShapeComposeFunction.Mask:
                        if (!isStuffBehind)
                        {
                            goto ContinueMainLoop;
                        }
                        break;
                    default:
                        break;
                }
                // check above
                for (int i = 0; i < idx; i++)
                {
                    if (_Objects[i]._Shape.IsPointInside( point))
                    {
                        switch (_Objects[i]._Function)
                        {
                            case ShapeComposeFunction.Add:
                                goto ContinueMainLoop;

                            case ShapeComposeFunction.Subtract:
                                goto ContinueMainLoop;

                            default:
                                break;
                        }
                    }
                    else if(_Objects[i]._Function == ShapeComposeFunction.Mask)
                    {
                        goto ContinueMainLoop;
                    }
                }

                // the nearest shape is found!
                return;

            ContinueMainLoop:
                outs[idx] = true;
            }

            // if no shape was found, return standard values.
            nearestObject = null;
            nearestPoint = _Transform.Position;
            distance = float.MaxValue;
        }
        void GetNearestShape(ref Vector2 point, ref bool[] outs, out ShapeObject nearestShapeObject, out float distance, out Vector2 nearestPoint)
        {
            nearestShapeObject = _Objects[0];
            nearestPoint = _Transform._Position;
            distance = float.MaxValue;
            int idx = 0;
            for(int i = 0; i < outs.Length; i++)
            {
                if (outs[i])
                    continue;

                if (_Objects[i]._Shape.GetDistanceToEdge(point) < distance)
                {
                    nearestShapeObject = _Objects[i];
                    distance = nearestShapeObject._Shape.GetDistanceToEdge(point);
                    idx = i;
                }
            }
            outs[idx] = true;

            nearestPoint = nearestShapeObject._Shape.GetNearestPointOnEdge(point);
        }
        #endregion

        internal override Vector2 GetPositionFromT(float t)
        {
            throw new NotImplementedException();
        }

        internal override float GetEdgePathValueFromPoint(ref Vector2 point)
        {
            throw new NotImplementedException();
        }

        internal override Vector2 GetTangent(ref Vector2 position)
        {
            throw new NotImplementedException();
        }

        internal override Vector2 GetNormal(ref Vector2 position)
        {
            throw new NotImplementedException();
        }
        /// <summary>
        /// Clones the ShapeComposition.
        /// </summary>
        /// <returns>an object with the type ShapeComposition</returns>
        public override object Clone()
        {
            ShapeComposition obj = new ShapeComposition((Shape)_Objects[_Objects.Count - 1]._Shape.Clone());
            for (int i = _Objects.Count - 2; i >= 0; i--)
            {
                obj.Add((Shape)_Objects[i]._Shape.Clone(), _Objects[i]._Function);
            }
            obj._Transform = (Transformation2D)_Transform.Clone();
            return obj;
        }

        /// <summary>
        /// Releases all references inside of the class.
        /// </summary>
        public override void Dispose()
        {
            while (_Objects.Count > 0)
            {
                _Objects[0]._Shape.Dispose();
                _Objects.RemoveAt(0);
            }
            _Objects = null;

            base.Dispose();
        }

        /// <summary>
        /// Gets the enumeration type which is mapped to this kind of object
        /// </summary>
        /// <returns>the geometry typee of this object</returns>
        public override GeometryType GetGeometryType()
        {
            return GeometryType.ShapeComposition;
        }
    }
}
